package com.lbiuse.wx.api;

import com.lbiuse.wx.AccountCache;
import com.lbiuse.wx.WechatCacheManager;
import com.lbiuse.wx.bean.AccessToken;
import com.lbiuse.wx.bean.Account;
import com.lbiuse.wx.bean.media.Media;
import com.lbiuse.wx.bean.media.MediaId;
import com.lbiuse.wx.consts.MediaType;
import com.lbiuse.wx.util.WxHttpUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * 基础API
 *
 * @author jinglupeng.cc
 */
public class BaseAPI {

    private static Logger logger = Logger.getLogger(BaseAPI.class);

    private final static String UPLOAD_MEDIA_URL = "http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE";
    private final static String DOWN_MEDIA_URL = "http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID";
    public static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";

    /**
     * 直接从微信公众平台获取AccessToken
     *
     * @param appId     -第三方用户唯一凭证
     * @param appSecret -第三方用户唯一凭证密钥，即appsecret
     * @return
     */
    public static AccessToken getAccessTokenFromServer(String appId,
                                                       String appSecret) {
        String url = ACCESS_TOKEN_URL.replaceAll("APPID", appId).replaceAll(
                "APPSECRET", appSecret);
        AccessToken token = WxHttpUtils.get(url, AccessToken.class);
        if (token.isSuccess()) {
            token.setCreateTime(System.currentTimeMillis());
            WechatCacheManager.cacheAccessToken(appId, token);
            return token;
        }
        logger.error("获取AccessToken失败，AppId:" + appId + ",错误码："
                + token.getErrcode());
        return new AccessToken(-2, "获取AccessToken失败");
    }

    /**
     * 获取AppSecret，为了减少AppSecret在系统中不必要的暴露，对AppSecret进行统一管理
     *
     * @param appId -第三方用户唯一凭证
     * @return
     */
    protected synchronized static String getAppSecret(String appId) {
        Account account = AccountCache.get(appId);
        if (account == null) {
            logger.error("未找到appId[" + appId + "]的信息！");
            return null;
        }
        return account.getAppSecret();
    }

    /**
     * 获取AccessToken，优先从缓存获取。获取AccessToken接口每天可获取2000次，每次有效期7200秒，
     * 获取新的AccessToken后旧的立刻失效。
     * 所以需要对AccessToken进行缓存，如果多个系统需要同时调用微信接口，则必须对AccessToken进行统一管理
     *
     * @param appId -第三方用户唯一凭证
     * @return
     */
    public synchronized static String getAccessToken(String appId) {
        AccessToken cacheToken = WechatCacheManager.getAccessToken(appId);
        if (cacheToken != null) {

            return cacheToken.getAccess_token();
        }
        String appSecret = getAppSecret(appId);
        if (appSecret == null) {
            logger.error("获取Appsecret失败，AppId:" + appId);
            return null;
        }
        AccessToken accessToken = getAccessTokenFromServer(appId, appSecret);
        if (!accessToken.isSuccess()) {
            logger.error("从微信服务器获取Appsecret失败，AppId:" + appId);
            return null;
        }
        logger.debug("Token:" + accessToken.getAccess_token());
        return accessToken.getAccess_token();
    }

    /**
     * 上传多媒体文件到微信服务器
     *
     * @param appId -第三方用户唯一凭证
     * @param file  -文件
     * @param type  -媒体文件类型，分别有图片（image）、语音（voice）、视频（video）和缩略图（thumb）
     * @return
     */
    public static MediaId uploadMedia(String appId, File file, MediaType type) {
        if (!type.checkSizeLimit(file)) {
            logger.error("上传多媒体文件失败，文件大小超过限制。限制为： " + type.getLimitSize()
                    + "。文件地址：" + file.getAbsolutePath());
            return new MediaId(-3, "上传文件大小超过限制！");
        }
        String token = getAccessToken(appId);
        if (token == null) {
            logger.error("上传多媒体文件失败，获取AccessToken失败！");
            return new MediaId(-4, "获取AccessToken失败！");
        }
        String url = UPLOAD_MEDIA_URL.replace("ACCESS_TOKEN", token).replace(
                "TYPE", type + "");
        return WxHttpUtils.uploadMedia(url, file, MediaId.class);
    }

    /**
     * 从微信服务器下载多媒体文件，该方法将文件数据缓存到内存中不做进一步处理，因此当从附件对象中读取数据结束后应该迅速将附件对象置空。
     *
     * @param appId   -第三方用户唯一凭证
     * @param mediaId -媒体文件ID
     * @return
     */
    public static Media downMedia(String appId, String mediaId) {
        String token = getAccessToken(appId);
        if (token == null) {
            logger.error("下载多媒体文件失败，获取AccessToken失败！");
            return new Media(-4, "获取AccessToken失败！");
        }
        String url = DOWN_MEDIA_URL.replace("ACCESS_TOKEN", token).replace(
                "MEDIA_ID", mediaId);
        try {
            return WxHttpUtils.downloadMedia(url);
        } catch (IOException e) {
            logger.error("下载多媒体文件失败，错误信息：" + e.getMessage());
            return new Media(-5, "未知失败！");
        }
    }

    /**
     * 用于和微信公众平台对接，验证消息是否来自微信公众平台
     *
     * @param token
     * @param signature -微信加密签名，signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
     * @param timestamp -时间戳
     * @param nonce     -随机数
     * @return
     */
    public static final boolean checkSignature(String token, String signature,
                                               String timestamp, String nonce) {
        List<String> params = new ArrayList<String>();
        params.add(token);
        params.add(timestamp);
        params.add(nonce);
        Collections.sort(params, new Comparator<String>() {
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        });
        String temp = params.get(0) + params.get(1) + params.get(2);
        return DigestUtils.sha1Hex(temp).equals(signature);
    }
}
